package com.hexacta.hrs.web.struts.actions;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.time.DateUtils;
import org.vectrics.common.util.DateUtil;
import org.vectrics.employee.Employee;
import org.vectrics.user.Person;

import com.hexacta.hrs.securitygroups.PermissionName;
import com.hexacta.hrs.services.crud.CrudService;
import com.hexacta.hrs.services.employee.EmployeeService;
import com.hexacta.hrs.services.timeoff.TimeOffService;
import com.hexacta.hrs.spring.PersonRetriever;
import com.hexacta.hrs.timeoff.AvailableLicenseDays;
import com.hexacta.hrs.timeoff.CertifiedTimeOff;
import com.hexacta.hrs.timeoff.ObjectListTimeOffType;
import com.hexacta.hrs.timeoff.Status;
import com.hexacta.hrs.timeoff.TimeOff;
import com.hexacta.hrs.timeoff.TimeOffStatusDetail;
import com.hexacta.hrs.timeoff.TimeOffType;
import com.hexacta.hrs.timeoff.TransitionFactory;

/**
 * Action de Struts para {@link TimeOff}s.
 * 
 * Created on 20/03/2009
 * 
 * @author pgarcia
 * @author lfilippelli
 */
public class TimeOffAction extends CrudAbstractAction<TimeOff> {

    private static final int WORKING_DAYS = 5;

    private static final int MAX_LENGHT_NOTES = 500;

    private static final long serialVersionUID = -937469985431979704L;

    private boolean certificatePresented;

    private boolean requiredOk = true;

    private Integer cancelledDays;

    private Map<String, String> certificates;

    private Collection<Employee> employeeList;

    private EmployeeService employeeService;

    private PersonRetriever personRetriever;

    private ApprovalNeededMailSender approvalNeededMailSender;

    private String selectedAssignedById;

    private String selectedAssignedByName;

    private Date selectedDateFrom;

    private Date selectedDateTo;

    private String selectedEmployeeId;

    private String selectedEmployeeName;

    private String selectedStatus;

    private String selectedTimeOffType;

    /** Variable que indica que se est� creando un Time Off. */
    private boolean created = false;

    /** Variable que indica que si se permite cambiar el estado al Time Off. */
    private boolean changeStatusAllowed;

    /** Variable que indica que el Time Off puede pasar a Confirmed. */
    private boolean confirm;

    /** Variable que indica que el Time Off puede pasar a Approved. */
    private boolean approved;

    /** Variable que indica que el Time Off puede pasar a Confirmed. */
    private boolean edited;

    /** Variable que indica si el Time Off esta Approved. */
    private boolean timeOffApproved;

    /**
     * Variable que indica si el Time Off puede ser creado solo para el usuario
     * actual.
     */
    private boolean ownTimeOff = false;

    /** Un {@link TimeOffService} a usar. */
    private TimeOffService timeOffService;

    private String exceedExams;

    private String exceedComp;

    private String notWeekVacation;

    /*
     * ----------------------------------------------------------------------
     * GETTERS Y SETTERS
     */

    public void setRequiredOk(final boolean requiredOk) {
        this.requiredOk = requiredOk;
    }

    public boolean isRequiredOk() {
        return requiredOk;
    }

    /**
     * @return the notWeekVacation
     */
    public String getNotWeekVacation() {
        return notWeekVacation;
    }

    /**
     * @param notWeekVacation
     *            the notWeekVacation to set
     */
    public void setNotWeekVacation(final String notWeekVacation) {
        this.notWeekVacation = notWeekVacation;
    }

    /**
     * @return the approvalNeededMailSender
     */
    public ApprovalNeededMailSender getApprovalNeededMailSender() {
        return approvalNeededMailSender;
    }

    /**
     * @param approvalNeededMailSender
     *            the approvalNeededMailSender to set
     */
    public void setApprovalNeededMailSender(final ApprovalNeededMailSender approvalNeededMailSender) {
        this.approvalNeededMailSender = approvalNeededMailSender;
    }

    public PersonRetriever getPersonRetriever() {
        return personRetriever;
    }

    public EmployeeService getEmployeeService() {
        return employeeService;
    }

    public String getExceedComp() {
        return exceedComp;
    }

    public void setExceedComp(final String exceedComp) {
        this.exceedComp = exceedComp;
    }

    public TimeOffService getTimeOffService() {
        return timeOffService;
    }

    /** {@inheritDoc} */
    @Override
    protected CrudService<TimeOff> getCrudService() {
        return timeOffService;
    }

    public String getExceedExams() {
        return exceedExams;
    }

    public void setExceedExams(final String exceedExams) {
        this.exceedExams = exceedExams;
    }

    /** Obtiene una lista de todos los Status Posibles menos el NEW. */
    public List<Status> getAllStatuses() {
        return Status.getValidTypes();
    }

    /** Obtiene una lista de todos los Time Off Types. */
    public List<ObjectListTimeOffType> getAllTimeOffTypes() {

        List<ObjectListTimeOffType> timeOffTypeList = new ArrayList<ObjectListTimeOffType>();

        TimeOffType[] timeOffTypesEnum = TimeOffType.values();
        for (TimeOffType element : timeOffTypesEnum) {
            ObjectListTimeOffType timeOffType = new ObjectListTimeOffType();
            String name = element.name();
            timeOffType.setId(name);
            name = name.replaceAll("_", " ");
            timeOffType.setName(name);
            timeOffTypeList.add(timeOffType);
        }

        return timeOffTypeList;
    }

    /**
     * Obtiene una lista de los valores posibles para presentar los
     * certificados.
     */
    public Map<String, String> getCertificates() {
        if (certificates == null) {
            setCertificates(new HashMap<String, String>());
            certificates.put("true", "El empleado presenta certificado");
            certificates.put("false", "El empleado NO presenta certificado");
        }
        return certificates;
    }

    public void setCertificates(final Map<String, String> certificates) {
        this.certificates = certificates;
    }

    /** Obtiene la lista de empleados. */
    public Collection<Employee> getEmployeeList() {
        if (!isOwnTimeOff()) {
            employeeList = employeeService.getAllCurrentEmployees();
        }

        return employeeList;
    }

    public void setCancelledDays(final Integer cancelledDays) {
        this.cancelledDays = cancelledDays;
    }

    public Integer getCancelledDays() {
        return cancelledDays;
    }

    /**
     * Devuelve si el usuario logueado tiene o no permiso para crear solo sus
     * propios TimeOff. En caso de ser as�, setea employeeList solo con s�
     * mismo.
     * 
     * @return
     */
    public boolean hasCreateOwnTimeOffPermission() {

        if (isCreated()) {
            getEmployeeList();
            if (hasPermission(PermissionName.CREATE_TIMEOFF.toString())
                    && !hasPermission(PermissionName.CREATE_ALL_TIMEOFF.toString())) {
                setOwnTimeOff(true);
                CollectionUtils.filter(employeeList, new PredicateFilterEmployeeByPerson(getAuthPerson().getId()));
                setSelectedEmployeeName(employeeList.iterator().next().getPerson().getName());
                setSelectedEmployeeId(employeeList.iterator().next().getId().toString());
                setCreated(false);
                return true;
            }
        }

        return false;
    }

    @Override
    public String create() {
        setCreated(true);
        return super.create();
    }

    public String getSelectedAssignedById() {
        return selectedAssignedById;
    }

    public String getSelectedAssignedByName() {
        return selectedAssignedByName;
    }

    public Date getSelectedDateFrom() {
        return selectedDateFrom;
    }

    public Date getSelectedDateTo() {
        return selectedDateTo;
    }

    public String getSelectedEmployeeId() {
        return selectedEmployeeId;
    }

    public String getSelectedEmployeeName() {
        return selectedEmployeeName;
    }

    public String getSelectedStatus() {
        return selectedStatus;
    }

    public String getSelectedTimeOffType() {
        return selectedTimeOffType;
    }

    public boolean isApproved() {
        return approved;
    }

    public boolean isCertificatePresented() {
        return certificatePresented;
    }

    public boolean isCertifiedTimeOff() {
        return getModel().isCertifiedTimeOff();

    }

    public boolean isEdited() {
        return edited;
    }

    public boolean isTimeOffApproved() {
        timeOffApproved = !approved && !edited && !confirm
                && TransitionFactory.getInstance().isEditAllowed(getModel().getCurrentStatusType());
        return timeOffApproved;
    }

    public boolean isConfirm() {
        return confirm;
    }

    public void setConfirm(final boolean confirm) {
        this.confirm = confirm;
    }

    public void setApproved(final boolean approved) {
        this.approved = approved;
    }

    public void setCertificatePresented(final boolean certificatePresented) {
        this.certificatePresented = certificatePresented;
    }

    /**
     * Cheque si el usuario tiene o no la posibilidad de editar el status de un
     * Time Off. En primer lugar verifica si tiene los permisos para hacerlo. Si
     * los tiene, verifica si es posible realizar un cambio de status segun el
     * status actual del Time Off.
     * 
     * @return
     */
    public boolean isChangeStatusAllowed() {
        if ((hasPermission(PermissionName.APPROVE_REJECT_ALL_TIMEOFF.toString()) || hasPermission(PermissionName.APPROVE_REJECT_TIMEOFF
                .toString()))
                && !timeOffApproved
                || hasPermission(PermissionName.CONFIRM_CANCEL_ALL_TIMEOFF.toString()) && timeOffApproved) {
            changeStatusAllowed = !approved && !edited && !confirm
                    && TransitionFactory.getInstance().isChangeStatusAllowed(getModel().getCurrentStatusType());
            return changeStatusAllowed;
        }
        return false;
    }

    public void setChangeStatusAllowed(final boolean changeStatusAllowed) {
        this.changeStatusAllowed = changeStatusAllowed;
    }

    public void setEdited(final boolean edited) {
        this.edited = edited;
    }

    public void setEmployeeList(final Collection<Employee> employeeList) {
        this.employeeList = employeeList;
    }

    public void setEmployeeService(final EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    public void setPersonRetriever(final PersonRetriever personRetriever) {
        this.personRetriever = personRetriever;
    }

    public void setSelectedAssignedById(final String selectedAssignedToId) {
        selectedAssignedById = selectedAssignedToId;
    }

    public void setSelectedAssignedByName(final String selectedAssignedByName) {
        this.selectedAssignedByName = selectedAssignedByName;
    }

    public void setSelectedDateFrom(final Date dateFrom) {
        selectedDateFrom = dateFrom;
    }

    public void setSelectedDateTo(final Date dateTo) {
        selectedDateTo = dateTo;
    }

    public void setSelectedEmployeeId(final String selectedEmployeeId) {
        this.selectedEmployeeId = selectedEmployeeId;
    }

    public void setSelectedEmployeeName(final String selectedEmployeeName) {
        this.selectedEmployeeName = selectedEmployeeName;
    }

    public void setSelectedStatus(final String selectedStatus) {
        this.selectedStatus = selectedStatus;
    }

    public void setSelectedTimeOffType(final String selectedTimeOffType) {
        this.selectedTimeOffType = selectedTimeOffType;
    }

    public void setTimeOffService(final TimeOffService timeOffService) {
        this.timeOffService = timeOffService;
    }

    // ----------------------------------------------------------------------

    /**
     * Metodo encargado de setear que se cambia de estado del timeOff.
     * 
     * Si los posibles Estados contienen approved se setea la variable set
     * approved para solo mostrar los campos especificados. De lo contrario, se
     * pasa a Confirmed/ Rejected por lo cual se setea la otra variable. TODO
     * �estas variables son excluyentes? se puede mejorar mucho el c�digo si
     * lo son.
     */
    public String changeStatus() {
        if (getPossibleStatus().contains(Status.APPROVED)) {
            setApproved(true);
        } else {
            setConfirm(true);
        }
        setSelectedStatus(StringUtils.EMPTY);
        setMappedRequest("save");
        return SUCCESS;
    }

    /**
     * Devuelve el Model del Action.
     */
    @Override
    public TimeOff getModel() {
        TimeOff timeOff = super.getModel();
        if (timeOff.getCurrentStatusDetail() != null) {
            if (StringUtils.isEmpty(getSelectedStatus()) && !isApproved() && !isConfirm()) {
                setSelectedStatus(timeOff.getCurrentStatusDetail().getStatus().name());
            }
            if (timeOff.getCurrentStatusDetail().getApprovedBy() != null) {
                setSelectedAssignedByName(timeOff.getCurrentStatusDetail().getApprovedBy().getPerson().getFullName());
                setSelectedAssignedById(timeOff.getCurrentStatusDetail().getApprovedBy().getPerson().getId().toString());
            }
            setSelectedEmployeeName(timeOff.getEmployee().getPerson().getFullName());
            setSelectedEmployeeId(timeOff.getEmployee().getId().toString());
            setSelectedTimeOffType(timeOff.getTimeOffType().toString());
            if (getSelectedDateFrom() == null) {
                setSelectedDateFrom(timeOff.getDateFrom());
            }
            if (getSelectedDateTo() == null) {
                setSelectedDateTo(timeOff.getDateTo());
            }
        }
        return timeOff;
    }

    /**
     * Obtiene los estados posibles de acuerdo al {@link Status} y
     * {@link TimeOffType} seleccionados.
     * 
     * @return una lista de {@link Status} posibles
     */
    public ArrayList<Status> getPossibleStatus() {
        if (StringUtils.isEmpty(getSelectedStatus()) && !isApproved() && !isConfirm()
                || getModel().getCurrentStatusDetail() == null)
            return TransitionFactory.getInstance().getPossibleStatus(null, null);
        if (getModel().getCurrentStatusType() != null) {
            ArrayList<Status> statuses = TransitionFactory.getInstance().getPossibleStatus(
                    getModel().getCurrentStatusType(), TimeOffType.valueOf(getSelectedTimeOffType()));
            /** Si esta en approved, no tengo que mostrar REQUESTED */
            if (getModel().getCurrentStatusType() == Status.APPROVED) {
                statuses.remove(Status.REQUESTED);
            }
            return statuses;
        }
        return TransitionFactory.getInstance().getPossibleStatus(Status.valueOf(getSelectedStatus()),
                TimeOffType.valueOf(getSelectedTimeOffType()));
    }

    /** {@inheritDoc} */
    @Override
    public void prepare() throws Exception {
        TimeOff model;
        if (getRequestId() == 0) {
            if (isCertifiedTimeOffType()) {
                model = getTimeOffService().createNewCertifiedTimeOff();
            } else {
                model = getTimeOffService().createNew();
            }
        } else {
            if (isCertifiedTimeOffType()) {
                model = getTimeOffService().searchCertifiedTimeOffById(getRequestId());
            } else {
                model = getTimeOffService().searchById(getRequestId());
            }
        }
        setModel(model);
    }

    /**
     * Se verifica si es CertifiedTimeOff (para serlo tiene q ser del tipo
     * Examen o Enfermedad)
     */
    private boolean isCertifiedTimeOffType() {
        return !StringUtils.isEmpty(selectedTimeOffType)
                && (getSelectedTimeOffType().equals(TimeOffType.EXAMEN.toString()) || getSelectedTimeOffType().equals(
                        TimeOffType.ENFERMEDAD.toString()));
    }

    /**
     * Metodo que realiza todas las validaciones al llenar formularios en
     * time-off.
     */
    @Override
    public void validate() {
        /** Valido que el usuario halla completado los campos requeridos */
        validateRequiredFieldsOk();
        /**
         * Si los campos requeridos fueron completados correctamente, luego se
         * pasa a validar el contenido l�gico de los datos ingresados.
         */
        if (isRequiredOk()) {
            validateData();
        }

    }

    /**
     * Verifica si la transacci�n actual corresponde a la edici�n de un
     * time-off. Si se trata de una edici�n, se compensan los d�as editados.
     */
    private void compenseCancelledDays() {
        if (isEdited()) {
            addCancelledDays();
        }
    }

    private void validateData() {
        /** Valido la transicion cuando NO es el listado inicial */
        if (isInitialListing()) {
            addFieldError("invalidTransition", "Status transition is not valid");
        }
        // this.validateLogicalContentOfDates();
        Integer oldQuantity = calculateAvailableDaysBeetweenOldDates();
        /** Validaciones del modelo */
        validateModel(oldQuantity);
        /**
         * Validaciones del modelo solo valida para TimeOffType iguales a
         * Examen.
         */
        validateModelIfExamLicence(oldQuantity);
    }

    /**
     * Determina si es el listado inicial.
     */
    private boolean isInitialListing() {
        return !getPossibleStatus().contains(Status.valueOf(getSelectedStatus())) && getActionMethod() != null
                && !isEdited();
    }

    /**
     * Hace las validaciones del modelo.
     */
    private void validateModel(Integer oldQuantity) {
        if ((getNotWeekVacation() == null || !getNotWeekVacation().equals("bypass")) && getActionMethod() != null) {
            if (TimeOffType.VACACIONES.compareTo(returnTimeOffrepleacedCharacters()) == 0) {
                int selectedDays = DateUtil.getWorkingDaysBetween(getSelectedDateFrom(), getSelectedDateTo());
                Employee employeeAux = getEmployeeService().searchById(
                        NumberUtils.toLong(getSelectedEmployeeId(), Long.MIN_VALUE));
                if (isApproved() && oldQuantity == selectedDays) {
                    /** si voy a aprobar seteo el old Quantity a 0 */
                    oldQuantity = 0;
                }
                /** Fecha de ingreso del empleado. */
                Date enterDate = employeeAux.getDateStart();
                /**
                 * Se calcula si el empleado tiene m�s de 6 meses de
                 * antig�edad
                 */
                validationHoliday(oldQuantity, selectedDays, employeeAux, enterDate);
            }
        } else {
            setNotWeekVacation("");
        }
    }

    /**
     * Se calcula si el empleado tiene m�s de 6 meses de antig�edad. En
     * funci�n de ello, se verifica que el empleado se tome las vacasiones de
     * una sola vez.
     */
    private void validationHoliday(final Integer oldQuantity, final int selectedDays, final Employee employeeAux,
            final Date startDate) {
        if (!DateUtil.roundToDay(DateUtils.addMonths(startDate, 6)).after(DateUtil.getCurrentDate())) {
            /**
             * El empleado no tiene menos de 6 meses de antig�edad. Se
             * verifica si el empleado tiene d�as disponibles.
             */
            validateAvailableLicenceDays(oldQuantity, selectedDays, employeeAux);
        } else {
            /**
             * Si el empelado tiene menos de 6 meses de antiguedad, se chequea
             * que se est� tomando todos los d�as juntos.
             */
            if (!getTimeOffService().isAllVacationDays(employeeAux, selectedDays)) {
                setNotWeekVacation("lowsix");
                addActionError("The amount of days selected must be multiple of 5");
                return;
            }
        }
    }

    /**
     * Hace las validaciones del modelo en caso de que la licencia sea por
     * EXAMEN.
     */
    private void validateModelIfExamLicence(Integer oldQuantity) {
        if ((getExceedExams() == null || !getExceedExams().equals("bypass")) && getActionMethod() != null) {
            if (TimeOffType.EXAMEN.compareTo(returnTimeOffrepleacedCharacters()) == 0) {
                int quantity = DateUtil.getWorkingDaysBetween(getSelectedDateFrom(), getSelectedDateTo());
                Employee employeeAux = getEmployeeService().searchById(
                        NumberUtils.toLong(getSelectedEmployeeId(), Long.MIN_VALUE));
                if (isApproved() && oldQuantity == quantity) {
                    /** si voy a aprobar seteo el old Quantity a 0 */
                    oldQuantity = 0;
                }
                if (!getTimeOffService().hasAvailableExamenDays(quantity, employeeAux, oldQuantity)) {
                    if (isApproved()) {
                        /** Si es approved queda igual. */
                        setExceedExams("ex");
                        addActionError("exceedExams");
                    }
                }
                /**
                 * Si no es el msj para el usuario, sale SIEMPRE (que se este
                 * CREANDO el time off)
                 */
                if (!isApproved() && !isConfirm() && Status.valueOf(getSelectedStatus()) != Status.CANCELLED) {
                    setExceedExams(getTimeOffService().amountAvailableExamenDays(quantity, employeeAux, oldQuantity)
                            .toString());
                    addActionError("exceedExams");
                    return;
                }
            }
        } else {
            /** Si era 'bypass', lo vuelvo a setear en vacio! */
            setExceedExams("");
        }

        /**
         * Validaciones del modelo solo valida para TimeOffType iguales a
         * 'Examen'.
         */
        if ((getExceedComp() == null || !getExceedComp().equals("bypass")) && isApproved()) {
            if (TimeOffType.PERIODO_COMPENSADO.compareTo(returnTimeOffrepleacedCharacters()) == 0) {
                Integer quantity = DateUtil.getWorkingDaysBetween(getSelectedDateFrom(), getSelectedDateTo());
                Employee employeeAux = getEmployeeService().searchById(
                        NumberUtils.toLong(getSelectedEmployeeId(), Long.MIN_VALUE));
                if (isApproved() && oldQuantity == quantity) {
                    /** si voy a aprobar seteo el old Quantity a 0. */
                    oldQuantity = 0;
                }
                if (!getTimeOffService().hasAvailableCompensationDays(quantity, employeeAux, oldQuantity)) {
                    /**
                     * TODO: Aca no seria un error si no mas bien un mensaje de
                     * q solo tiene X cantidad de dias disponibles. Pero no
                     * tiene que cortar la ejecucion.
                     */
                    setExceedComp("ex");
                    addActionError("exceedComp");
                    return;
                }
            }
        } else {
            /** Si era bypass, lo vuelvo a setear en vacio. */
            setExceedComp("");
        }
    }

    /**
     * Reemplaza ' ' por '_'
     */
    private TimeOffType returnTimeOffrepleacedCharacters() {
        return TimeOffType.valueOf(getSelectedTimeOffType().replace(' ', '_'));
    }

    /**
     * Verifica si el empleado dispone de los d�as de licencia solicitados.
     */
    private void validateAvailableLicenceDays(final Integer oldQuantity, final int selectedDays,
            final Employee employeeAux) {
        Integer availableDays = getTimeOffService().getAvailableLicenseDaysService()
                .getAvailableLicenseDays(employeeAux).getVacationsDays()
                + oldQuantity;
        if (!isEdited() && !getTimeOffService().hasAvailableVacationsDays(selectedDays, employeeAux, oldQuantity)) {
            addFieldErrorNotAvailableDays(selectedDays, employeeAux, availableDays);
            return;
        }
        if (isEdited() && !getTimeOffService().hasAvailableVacationsDays(selectedDays, employeeAux, oldQuantity)) {
            addFieldErrorNotAvailableDays(selectedDays, employeeAux, availableDays);
            return;
        }

        if (Status.valueOf(getSelectedStatus()) == Status.REQUESTED && availableDays >= WORKING_DAYS
                && selectFullWeeks(selectedDays)
                && (getNotWeekVacation() == null || !getNotWeekVacation().equals("bypass"))) {
            setNotWeekVacation("ex");
            addActionError("The amount of days selected must be multiple of 5");
            return;
        }
    }

    /**
     * Calcula si el empleado solicito licencia por semanas completas.
     * 
     * @param selectedDays
     * @return 'false' en caso contrario.
     */
    private boolean selectFullWeeks(final int selectedDays) {
        return selectedDays % 5 != 0;
    }

    /**
     * Emite mensaje de error en caso que el empleado no disponga de los d�as
     * solicitados.
     */
    private void addFieldErrorNotAvailableDays(final int selectedDays, final Employee employeeAux,
            final Integer availableDays) {
        addFieldError("vacationErrorNotAvailableDays", "The employee " + employeeAux.getPerson().getFullName()
                + " doesnt posses " + selectedDays + " vacation days." + " The available days are: " + availableDays
                + ".");
    }

    /**
     * Devuelve los d�as disponibles entre las fechas de inicio y fin de
     * licencia solicitados anteriormente. En caso de ser licencia por
     * VACACIONES, EXAMEN o PERIODO COMPENSADO, no se cuentan los fines de
     * semana. Se guarda esa cantidad para luego compensar.
     */
    private Integer calculateAvailableDaysBeetweenOldDates() {
        Integer oldQuantity = 0;
        if (getModel().getDateFrom() != null && getModel().getDateTo() != null) {
            if (!TimeOffType.VACACIONES.equals(returnTimeOffrepleacedCharacters())
                    && !TimeOffType.EXAMEN.equals(returnTimeOffrepleacedCharacters())
                    && !TimeOffType.PERIODO_COMPENSADO.equals(returnTimeOffrepleacedCharacters())) {
                oldQuantity = getModel().getDateTo().compareTo(getModel().getDateFrom());
            } else {
                oldQuantity = DateUtil.getWorkingDaysBetween(getModel().getDateFrom(), getModel().getDateTo());
            }
        }
        return oldQuantity;
    }

    /**
     * Valida el contenido l�gico de las fechas de inicio y fin de licencia.
     */
    // private void validateLogicalContentOfDates() {
    // /**
    // * valida que la fecha de inicio no sea posterior a la fecha de fin de
    // * licencia
    // */
    // if (this.getSelectedDateTo().before(this.getSelectedDateFrom())) {
    // addFieldError("dateToBeforeDateFromError",
    // "Date To must be greater than DateFrom");
    // }
    // this.validateConfirmedTimeoffBeforeCurrentDate();
    // this.validateLogicalContentOfDatesOnWeekend();
    // }

    /**
     * Valida que la fecha de inicio y fin de licencia no caiga un sabado o
     * domingo. En caso de que la licencia NO sea por VACACIONES, EXAMEN o
     * PERIODO_COMPENSADO, se permite que la fecha de INICIO SI caiga en esos
     * d�as y adem�s se calculan los fines de semana que hallan en el medio.
     */
    // private void validateLogicalContentOfDatesOnWeekend() {
    // if (DateUtil.getDayOfWeek(this.getSelectedDateTo()) == Calendar.SATURDAY
    // || DateUtil.getDayOfWeek(this.getSelectedDateTo()) == Calendar.SUNDAY) {
    // addFieldError("dateInWeekend",
    // "Date To cant fall in a saturday or sunday");
    // if (TimeOffType.VACACIONES.equals(returnTimeOffrepleacedCharacters())
    // ||
    // TimeOffType.EXAMEN.equals(TimeOffType.valueOf(selectedTimeOffType.replace(' ',
    // '_')))
    // || TimeOffType.PERIODO_COMPENSADO
    // .equals(TimeOffType.valueOf(selectedTimeOffType.replace(' ', '_')))) {
    // addFieldError("dateInWeekend2",
    // "Date From cant fall in a saturday or sunday");
    // }
    // }
    // }

    /**
     * Valida que el time-off sea confirmado antes de la fecha de caducaci�n
     * de la licencia.
     */
    // private void validateConfirmedTimeoffBeforeCurrentDate() {
    // if (Status.valueOf(this.getSelectedStatus()) == Status.CONFIRMED
    // && DateUtil.mayorIgual(this.getSelectedDateTo(),
    // DateUtil.getCurrentDate())) {
    // addFieldError("dateError",
    // "Date To cannot be greater than the current Date");
    // // requiredOk = false;
    // }
    // }

    /**
     * Valida que en caso de ser posible aprobar un time-off, el usuario ingrese
     * correctamente el nombre del responsable de aprobarlo.
     */
    private void validateAssignedByIdOk() {
        if (isApproved()) {
            if (StringUtils.isEmpty(getSelectedAssignedById())) {
                addFieldError("assignedByError", "Assigned By is required");
                setRequiredOk(false);
            } else if (!StringUtils.isNumeric(getSelectedAssignedById())) {
                addFieldError("assignedByError", "Assigned By is not valid");
                setRequiredOk(false);
            } else if (getSelectedAssignedById().equals(getSelectedEmployeeId())) {
                addFieldError("assignedByError", "Approved/Rejected By cannot be the Employee");
                setRequiredOk(false);
            }
        }
    }

    /**
     * Valida que se halla ingresado un tipo de time-off valido.
     */
    private void validateTimeOffTypeOk() {
        try {
            returnTimeOffrepleacedCharacters();
        } catch (NullPointerException ex) {
            addFieldError("TimeOffTypeError", "Time Off Type is required");
            setRequiredOk(false);
        } catch (IllegalArgumentException ex) {
            addFieldError("TimeOffTypeError", "Time Off Type is not valid");
            requiredOk = false;
        }
    }

    /**
     * Valida que la longitud del campo notes no exceda los 500 caracteres.
     */
    private void validateLenghtNotesOk() {
        if (!getModel().getNotes().isEmpty() && getModel().getNotes().length() > MAX_LENGHT_NOTES) {
            addFieldError("notesError", "Notes maximum number of characters is " + MAX_LENGHT_NOTES);
            setRequiredOk(false);
        }
    }

    /**
     * Valida que se halla ingresado un status v�lido.
     */
    private void validateStatusOk() {
        try {
            Status.valueOf(getSelectedStatus());
        } catch (NullPointerException ex) {
            addFieldError("statusError", "Status is required");
            setRequiredOk(false);
        } catch (IllegalArgumentException ex) {
            addFieldError("statusError", "Status is not valid");
            setRequiredOk(false);
        }
    }

    /**
     * Valida que se hallan ingresado las fechas de licencia.
     */
    private void validateDatesOk() {
        if (getSelectedDateFrom() == null) {
            addFieldError("dateFromError", "Date From is required");
            setRequiredOk(false);
        }
        if (getSelectedDateTo() == null) {
            addFieldError("dateToError", "Date To is required");
            setRequiredOk(false);
        }
    }

    /**
     * Carga los d�as de vacasiones que fueron cancelados al editar un
     * time-off
     */
    private void validateEmployeeIdOk() {
        if (StringUtils.isEmpty(getSelectedEmployeeId()) || !StringUtils.isNumeric(getSelectedEmployeeId())) {
            addFieldError("employeeError", "Employee is required");
            setRequiredOk(false);
        }
    }

    /**
     * Valida el ingreso del tipo de dato correcto en todos los campos
     * requeridos. Se agregan fieldErrors correspodiendtes al tipo de errores
     * hallados.
     * 
     * @return 'true' si todas las validaciones pasaron satisfactoriamente,
     *         'false' en otro caso.
     */
    private void validateRequiredFieldsOk() {
        validateStatusOk();
        validateTimeOffTypeOk();
        validateEmployeeIdOk();
        validateDatesOk();
        validateLenghtNotesOk();
        validateAssignedByIdOk();
    }

    /**
     * Carga los d�as de vacaciones que fueron cancelados al editar un
     * time-off
     */
    private void addCancelledDays() {
        Employee employee = getEmployeeService().searchById(Long.parseLong(getSelectedEmployeeId()));
        AvailableLicenseDays availableLicenceDays = getTimeOffService().getAvailableLicenseDaysService()
                .getAvailableLicenseDays(employee);

        if (TimeOffType.PERIODO_COMPENSADO.equals(TimeOffType.valueOf(getSelectedTimeOffType().replace(' ', '_')))) {
            availableLicenceDays.setCompensationDays(availableLicenceDays.getCompensationDays() + getCancelledDays()
                    * 2);
        }

        if (TimeOffType.EXAMEN.equals(TimeOffType.valueOf(getSelectedTimeOffType().replace(' ', '_')))) {
            availableLicenceDays.setStudyLicenseDays(availableLicenceDays.getStudyLicenseDays() + getCancelledDays()
                    * 2);
        }
    }

    /**
     * Realiza las acciones pertinentes antes de editar un time-off. Cambia el
     * Status a 'Requested' y guarda los d�as de modificaci�n para
     * agregarlos posteriormente.
     */
    @Override
    protected void doActionsBeforeEdit() {
        setEdited(true);
        setSelectedStatus(Status.REQUESTED.toString());
        setCancelledDays(DateUtil.getWorkingDaysBetween(getSelectedDateFrom(), getSelectedDateTo()));
        super.doActionsBeforeEdit();
    }

    /**
     * Realiza las acciones pertinentes luego de editar un time-off.
     */
    @Override
    protected void doActionsBeforeSave() {
        TimeOff model = getModel();
        if (model.getEmployee() == null) {
            model.setEmployee(getEmployeeService().searchById(
                    NumberUtils.toLong(getSelectedEmployeeId(), Long.MIN_VALUE)));
        }
        model.setTimeOffType(returnTimeOffrepleacedCharacters());
        /**
         * Se fija si de 'Approved' no paso a 'Cancelled' lo cual significa que
         * no se uso el timeOff, por lo cual no importan las fechas modificas
         * sino las originales.
         */
        if (Status.valueOf(getSelectedStatus()).compareTo(Status.CANCELLED) != 0) {
            restartDates(model);
        }

        Person currentPerson = getPersonRetriever().getLoggedInPerson();
        model.changeCurrentStatus(Status.valueOf(getSelectedStatus()),
                getEmployeeService().searchById(NumberUtils.toLong(getSelectedAssignedById(), Long.MIN_VALUE)),
                currentPerson);
        if (model.isCertifiedTimeOff()) {
            CertifiedTimeOff cto = (CertifiedTimeOff) model;
            cto.setCertificateName(returnTimeOffrepleacedCharacters().toString());
            cto.setCertificatePresented(isCertificatePresented());
        }
    }

    /**
     * Se actualizan los contadores si las fechas cambiaron.
     */
    private void restartDates(final TimeOff model) {
        model.changeDates(getSelectedDateFrom(), getSelectedDateTo());
    }

    /**
     * Realiza las acciones pertinentes luego de grabar un time-off. Recupera el
     * nombre e id del empleado y setea las fechas de licencia en 'null'.
     */
    @Override
    protected void doActionsAfterSave() {
        compenseCancelledDays();
        Person person = getModel().getEmployee().getPerson();
        if (getModel().getCurrentStatusType().equals(Status.REQUESTED)) {
            approvalNeededMailSender.sendApprovalRequest(getModel(), getRequest());
        }
        setSelectedEmployeeName(person.getFullName());
        setSelectedEmployeeId(getModel().getEmployee().getId().toString());
        setSelectedDateFrom(null);
        setSelectedDateTo(null);
    }

    /** {@inheritDoc} */
    /**
     * Devuelve el time-off seg�n los datos igresados por el usuario.
     */
    @Override
    protected TimeOff getSearchCriteria() {
        TimeOff criteria = new TimeOff();
        datesFilter(criteria);
        timeOffTypeFilter(criteria);
        employeeFilter(criteria);
        statusFilter(criteria);
        return criteria;
    }

    /**
     * Filtro por 'Status'. Si el 'Status' es 'NEW' no debe devolver nada
     * (seg�n el bug HRS-799)
     */
    private void statusFilter(final TimeOff criteria) {
        if (!StringUtils.isEmpty(getSelectedStatus())) {
            try {
                if (Status.valueOf(getSelectedStatus()) != Status.NEW) {
                    TimeOffStatusDetail timeOffStatusDetail = new TimeOffStatusDetail();
                    timeOffStatusDetail.setStatus(Status.valueOf(getSelectedStatus()));
                    criteria.addNewStatus(timeOffStatusDetail);
                } else {
                    criteria.setEmployee(new Employee());
                }
            } catch (Exception e) {
                criteria.setEmployee(new Employee());
            }
        }
    }

    /**
     * Filtro por fechas.
     */
    private void datesFilter(final TimeOff criteria) {
        /** Filtro por fecha Desde. */
        if (getSelectedDateFrom() != null) {
            criteria.setDateFrom(getSelectedDateFrom());
        }
        /** Filtro por fecha Hasta. */
        if (getSelectedDateTo() != null) {
            criteria.setDateTo(getSelectedDateTo());
        }
    }

    /**
     * Filtro por empleado. En caso de que no encuentre ninguna coincidencia,
     * setea un empleado vacio.
     */
    private void employeeFilter(final TimeOff criteria) {
        if (!StringUtils.isEmpty(getSelectedEmployeeName())) {
            long id = NumberUtils.toLong(getSelectedEmployeeId(), Long.MIN_VALUE);
            if (Long.MIN_VALUE != id) {
                criteria.setEmployee(getEmployeeService().searchById(id));
            } else {
                criteria.setEmployee(new Employee());
            }
        }
    }

    /**
     * Filtro por tipo de time-off. En caso de que no encuentre ninguna
     * coincidencia, setea un empleado vacio.
     */
    private void timeOffTypeFilter(final TimeOff criteria) {
        if (!StringUtils.isEmpty(selectedTimeOffType)) {
            try {
                criteria.setTimeOffType(returnTimeOffrepleacedCharacters());
            } catch (Exception e) {
                criteria.setEmployee(new Employee());
            }
        }
    }

    public void setOwnTimeOff(final boolean ownTimeOff) {
        this.ownTimeOff = ownTimeOff;
    }

    public boolean isOwnTimeOff() {
        return ownTimeOff;
    }

    // public void setInicial(String inicial) {
    // this.inicial = inicial;
    // }
    //
    // public String getInicial() {
    // return inicial;
    // }

    public void setCreated(final boolean created) {
        this.created = created;
    }

    public boolean isCreated() {
        return created;
    }

    public void sendApprovalReminders() {
        List<Person> approvers = timeOffService.getPendingApprovalEmployees();
        approvalNeededMailSender.remindersSender(approvers);
    }
}

class PredicateFilterEmployeeByPerson implements Predicate {

    private Long personId;

    @Override
    public boolean evaluate(final Object object) {
        Employee employee = (Employee) object;
        if (employee.getPerson() != null && employee.getPerson().getUser() != null
                && employee.getPerson().getUser().getUserName() != null) {
            if (employee.getPerson().getId().equals(personId))
                return true;
        }
        return false;
    }

    public PredicateFilterEmployeeByPerson(final Long personId) {
        this.personId = personId;
    }

}
